package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//UDP回显服务器
public class UdpEchoServer {
    //网络编程本质上是操作网卡
    //网卡不方便直接操作,但在操作系统内核中使用了一种叫"Socket"的文件来抽象表示网卡
    //因此进行网络通信就得先有一个"Socket"对象
    private DatagramSocket socket = null;

    //对于服务器来说,创建Socket对象的同时,必须要绑定一个指定具体的端口(port)
    //服务器是网络传输中被动的一方,如果操作系统随机分配端口,此时客户端就不知道这个端口到底指到哪的,就没法进行通信

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    //DatagramPacket为UDP传输数据的基本单位

    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器需要对很多客户端提供服务,所以得用一个循环一直站岗接受数据
        while(true) {
            //只要客户端过来就可以提供服务,

            // 1.读取客户端发来的请求
            //receive 方法是一个 输出 型参数需要 先构造好空白的 DatagramPacket 对象做参数,交给receive来进行填充
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //datagramPacket就相当于点餐纸条,把纸条写好了就交给receive处理
            socket.receive(requestPacket); //receive内部会针对参数(datagramPacket)对象填充数据,填充数据来自网卡
            //此时这个DatagramPacket(datagramPacket)是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来构造成一个字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength()); //获取数据构成字符串

            // 2.根据请求计算响应,由于此处是回显服务器所以请求跟响应相同
            String response = process(request);

            // 3.把回应写回到客户端,发送函数send的参数也是DatagramPacket,需要把这个Packet对象提前构造好
            //此处构造的响应对象不能用空的字节数组构造了,而是使用响应数据构造
            DatagramPacket responsePacket = new DatagramPacket( response.getBytes(),
                                                                response.getBytes().length,
                                                                requestPacket.getSocketAddress()
                                                                );
    //DatagramPacket 参数
//            response.getBytes() :传响应对象的这个字节数组
//            response.getBytes().length :指定长度.此处这个长度使用response.length()计算字符的个数也行,response.getBytes().length是计算字节的个数,操作计量单位不同,而"DatagramPacket只认字符不认字节",所以还是用response.getBytes().length
//            requestPacket.getSocketAddress() : 获取到客户端的IP和端口号(这两个信息存在requestPacket中所以点得出来),相当于取餐小票纸条(上面有客户端的信息 IP和端口号)
            socket.send(responsePacket); //send函数是指定要把数据发给哪个(指定的)客户端
            //4. 打印当前这次请求响应的处理中间结果
            System.out.printf("[%s:%d] req: %s; resp: %s\n",requestPacket.getAddress().toString(), //requestPacket.getAddress() : 获取到Packet里的IP
                                                            requestPacket.getPort(), //requestPacket.getPort() : 获取Packet里面的端口
                                                            request,response);
        }
    }

    //响应函数
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        //服务器端口号的指定可以自定义(1024 <--> 65535)
        UdpEchoServer server = new UdpEchoServer(9090);     //服务器在构造方法中,先指定好自己的端口,
        server.start();
    }
}